<?php
class SP_Database_Record
{
	private $base_name;
	private $root;
	private $reader;
	private $writer;
	private $table_name;
	private $name;
	private $primary_key;
	private $is_saved=false;
	private $validate_before_save=false;
	private $validate_before_set=true;
	private $fields=array();
	private $relation_fields=array();
	
	public function defineField(SP_Database_Field $field)
	{
		$new_field_name=strtolower($field->getModelName());
		if( $this->hasField($new_field_name) or $this->hasRelationField($new_field_name) )
			throw new exception('Field with name "'.$new_field_name.'" already exist');
		if( $field->hasRelation() )
		{
			$this->relation_fields[$new_field_name]=$field;
		}
		else
		{
			$this->fields[$new_field_name]=$field;
		}
		return $this;
	}
	
	public function hasField($field_name)
	{
		$name=strtolower($name);
		return isset($this->fields[$field_name]);
	}
	
	public function hasRelationField($field_name)
	{
		$name=strtolower($name);
		return isset($this->relation_fields[$field_name]);
	}
	
	public function takeField($name)
	{
		$name=strtolower($name);
		if($this->hasField($name)) return $this->fields[$name];
		if($this->hasRelationField($name)) return $this->relation_fields[$name];
		throw new exception('Record class "'.$this->name.'" has not "'.$name.'" field');
	}
	
	public function definePrimaryKey($field)
	{
		$this->fields['id']=$field;
		return $this;
	}	



	public function set($name,$value)
	{
		$field=$this->takeField($name);
		if( $relation=$field->getRelation() )
		{	
			if( $relation->getType() == 'one' )
			{
				if( get_class($value)!=$relation->getForeignClass() ) 
					return array('Значение поля "'.$name.'" должно быть объектом класса"'.$relation->getForeignClass().'"');
			}
			else
			{
				if( !is_array($value) )
					return array('Значение поля "'.$name.'" должно быть массивом объектов класса"'.$relation->getForeignClass().'"');
				if( !SP_Common_Functions::arrayHasOnlyClass($value,$relation->getForeignClass()) )
					return array('Значение поля "'.$name.'" должно быть массивом объектов класса"'.$relation->getForeignClass().'"');
			}
			$field->setValueForce($value);
		}
		else
		{
			if($this->validate_before_set)
			{
				if( !$field->setValue($value) )
					return $field->getValidateErrors();
			}
			else
			{
				$field->setValueForce($value);
			}
		}
		$this->is_saved=false;
		return true;
	}

	public function get($name)
	{
		$field=$this->takeField($name);
		if( $field->getRelation() )
		{
			if( !$field->getValue() )
			{	
				$this->takeRelationFieldValue($field);
			}
			return $field->getValue();
		}
		else
		{
			return $field->getValue();
		}
	}

	public function add($name,$value)
	{
		$field=$this->takeField($name);
		if ( !$relation=$field->getRelation() ) return false;
		if ( $relation->getType()=='one' ) return false;
		$ref_class=$relation->getRefClass();
		$base=$this->takeWriter();

		if( $ref_class )
		{
			$ref_record = new $ref_class();
			$query="SELECT `".$ref_record->takeField($relation->getForeignKey())->getBaseName()."` 
					FROM `".$ref_record->takeTableName()."` 
					WHERE `".$ref_record->takeField($relation->getLocalKey())->getBaseName()."`='".$this->takeField('id')->getValue()."'";
			$base_array=$base->getAssocField($query, $ref_record->takeField($relation->getForeignKey())->getBaseName(), $ref_record->takeField($relation->getForeignKey())->getBaseName());
			if( is_array($value) )
			{
				$query="INSERT INTO `".$ref_record->takeTableName()."` 
						(`".$ref_record->takeField($relation->getLocalKey())->getBaseName()."`,`".$ref_record->takeField($relation->getForeignKey())->getBaseName()."`) 
						VALUES ";
				foreach($value as $val)
				{
					if ( !is_numeric($val) ) $val=$val->takeField('id')->getValue();

					if( !array_key_exists($val, $base_array) )
					{
						$record_num++;
						$query.="('".$this->takeField('id')->getValue()."', '".$val."'),";
					}
				}
				if( $record_num==0 ) return false;
				$query=SP_Common_Functions::delLastChar($query);
			}
			else
			{

				if( !is_numeric($value) ) $value=$value->takeField('id')->getValue();
				if( !array_key_exists($value, $base_array) )
				{
					$query="INSERT INTO `".$ref_record->takeTableName()."` 
					(`".$ref_record->takeField($relation->getLocalKey())->getBaseName()."`,`".$ref_record->takeField($relation->getForeignKey())->getBaseName()."`) 
					VALUES ('".$this->takeField('id')->getValue()."', '".$value."')";
				}
			}
		}
		$base->query($query);
	}
	
	public function del($name,$value)
	{
		
	}

	public function __call($name,$values=null)
	{
		if(strlen($name) > 3)
		{
			$value=$values[0];
			
			$method=strtolower(substr($name,0,3));
			$field_name=substr($name,3);
			
			if( $method=='set' )
			{
				return $this->set($field_name, $value);
			}
			elseif( $method=='get' )
			{
				return $this->get($field_name);
			}
			elseif( $method=='add' )
			{	
				return $this->add($field_name,$value);
			}
		}
	}



	public function takeRelationFieldValue($field)
	{
		if( !$this->isNew() )
		{
			$base=$this->takeReader();
			$relation=$field->getRelation();
			$ref_class=$relation->getRefClass();
			if( $ref_class )
			{
				$class = $relation->getForeignClass();
				$record = new $class();
				$ref_record = new $ref_class();
				$inner_query="SELECT `".$ref_record->takeField($relation->getForeignKey())->getBaseName()."` FROM `".$ref_record->takeTableName()."` WHERE `".$ref_record->takeField($relation->getLocalKey())->getBaseName()."`='".$this->takeField('id')->getValue()."'";
				$query="SELECT * FROM `".$record->takeTableName()."` WHERE `".$record->takeField('id')->getBaseName()."` IN ($inner_query)";
				$base->query($query);
				$records=array();
				while($row=$base->fetchAssoc())
				{
					$record = new $class();
					$record->fromRow($row);
					$records[$record->takeField('id')->getValue()]=$record;
				}
				$field->setValue($records);
			}
			else
			{
				if( $relation->getType() == 'one' )
				{
					$class = $relation->getForeignClass();
					$record = new $class($this->base);
					$record->readWhere("`".$record->takeField($relation->getForeignKey())->getBaseName()."`='".$this->takeField($relation->getLocalKey())->getValue()."'");
					$field->setValue($record);
				}
				elseif( $relation->getType() == 'many' )
				{
					$records=array();
					$class = $relation->getForeignClass();
					$record = new $class($this->base);
					//var_dump($record);
					//echo $record->takeField($relation->getForeignKey())->getBaseName();
					$query = "SELECT * FROM `".$record->takeTableName()."` WHERE `".$record->takeField($relation->getForeignKey())->getBaseName()."`='".$this->takeField($relation->getLocalKey())->getValue()."'";
					$base->query($query);
					while($row=$base->fetchAssoc())
					{
						$record = new $class();
						$record->fromRow($row);
						$records[$record->takeField('id')->getValue()]=$record;
					}
					$field->setValue($records);
					//var_dump($this);
				}
				else
				{
					throw new exception('Wrong relation type in '.$this->table_name.' record in '.$field->getModelName().' field');
				}
			}
		}
	}

	public function validateValues()
	{
		foreach($this->fields as $field)
		{
			if( !$field->validate() ) return false;
		}
		return true;
	}
	
	public function save()
	{
		if($this->validate_before_save and !$this->validateValues()) return false;
		$this->saveRecord();
		$this->is_saved=true;
	}

	public function saveRecord()
	{
		$base=$this->takeWriter();
		$to_save=array();
		foreach($this->fields as $field) //формируем массив для сохранения
		{
			$to_save[$field->getBaseName()]=$field->getValue();
		}
		unset($to_save[$this->takeField('id')->getBaseName()]); //удаляем из сохранения id поле
		if( $this->isNew() ) // запись новая - делаем INSERT
		{
			$base->query(SP_Helpers_Query::insert($this->takeTableName(),$to_save));
			if( $base->getResult() ) //если успешно вставилось
			{
				$this->takeField('id')->setValue($base->getLastInsertId());
				$this->is_new=false;
				return true;
			}
			return false;
		}
		else //запись уже сохранена в базе - делаем UPDATE
		{
			foreach($this->fields as $field) //удаляем поля, которые не были изменены
			{
				if( !$field->getWasModified() ) unset($to_save[$field->getBaseName()]);
			}
			if( count($to_save)==0 ) return true; //если измененных полей ноль - возвращаем тру
			$where[$this->takeField('id')->getBaseName()]=$this->takeField('id')->getValue();
			$base->query(SP_Helpers_Query::update($this->takeTableName(),$to_save,$where,1));
			return (bool)$base->getResult();
		}
	}
	
	public function saveRelatedRecords()
	{
		$base=$this->takeWriter();
		foreach($this->relation_fields as $name => $field)
		{
			//если поле было изменено
			if( $field->getWasModified() or true )
			{
			
				$relation=$field->getRelation();
				$value=&$field->getValue();
				$ref_class=$relation->getRefClass();
				
				
				//если имеется $ref_class, значит отношение многие-ко-многим
				if( $ref_class )
				{
					if( empty($value) ) $value=array();
					
					$ref_record = new $ref_class();
					if( $relation->getUpdateMethod()=='cascade' )
					{
						//сохраняем новые и не сохраненные записи
						foreach($value as $record)
						{
							if ( $record->isNew() and !$record->isSaved() ) $record->save();
						}
						
						//переопределяем массив значений поля
						$this->takeRelationFieldValue($field);
						$model_array=&$field->getValue();
						//выбираем записи которые уже сохранены в базе
						echo $query="SELECT `".$ref_record->takeField($relation->getForeignKey())->getBaseName()."` 
						FROM `".$ref_record->takeTableName()."` 
						WHERE `".$ref_record->takeField($relation->getLocalKey())->getBaseName()."`='".$this->takeField('id')->getValue()."'";
						$base_array=$base->getAssocField($query, $ref_record->takeField($relation->getForeignKey())->getBaseName(), $ref_record->takeField($relation->getForeignKey())->getBaseName());
						$dont_touch=array_intersect_key($base_array, $model_array);
						$to_add=array_diff_key($model_array,$dont_touch);
						$to_del=array_diff_key($base_array, $dont_touch);
					}
				}
				else
				{
				}
			}
		}
	}
	
	public function delete()
	{
		if( !$this->isNew() )
		{
			$this->deleteRelatedRecords();
			$result=$this->deleteRecord();
			if ($result) $this->undefine();
			return $result;
		}
		return false;
	}
	
	public function deleteRecord()
	{
		$base=$this->takeWriter();
		$query="DELETE FROM `".$this->takeTableName()."` WHERE `".$this->takeField('id')->getBaseName()."`='".$this->takeField('id')->getValue()."' LIMIT 1";
		$base->query($query);
		return (bool)$base->getResult();
	}
	
	public function deleteRelatedRecords()
	{
		$base=$this->takeWriter();
		foreach($this->relation_fields as $name => $field)
		{
			$relation=$field->getRelation();
			$ref_class=$relation->getRefClass();
			$class = $relation->getForeignClass();
			$record = new $class();
			if( $ref_class )
			{
				$ref_record = new $ref_class();
				if( $relation->getDeleteMethod()=='cascade')
				{
					$inner_query="SELECT `".$ref_record->takeField($relation->getForeignKey())->getBaseName()."` FROM `".$ref_record->takeTableName()."` WHERE `".$ref_record->takeField($relation->getLocalKey())->getBaseName()."`='".$this->takeField('id')->getValue()."'";
					$query="DELETE FROM `".$record->takeTableName()."` WHERE `".$record->takeField('id')->getBaseName()."` IN ($inner_query)";
					$base->query($query);
					$query= "DELETE FROM `".$ref_record->takeTableName()."` WHERE `".$ref_record->takeField($relation->getLocalKey())->getBaseName()."`='".$this->takeField('id')->getValue()."'";
					$base->query($query);
				}
				elseif( $relation->getDeleteMethod()=='only_refs')
				{
					$query= "DELETE FROM `".$ref_record->takeTableName()."` WHERE `".$ref_record->takeField($relation->getLocalKey())->getBaseName()."`='".$this->takeField('id')->getValue()."'";
					$base->query($query);
				}
			}
			else
			{	
				if( $relation->getDeleteMethod()=='cascade')
				{
					$query="DELETE FROM `".$record->takeTableName()."` WHERE `".$record->takeField($relation->getForeignKey())->getBaseName()."`='".$this->takeField($relation->getLocalKey())->getValue()."'";
					$base->query($query);
				}
			}
		}
	}
	
	public function fromRow($row)
	{
		if(is_array($row))
		{
			foreach($this->fields as $field)
			{
				$field->setValueForce($row[$field->getBaseName()]);
			}
		}
	}
	
	public function readWhere($where, $native_sql=true)
	{
		if ($native_sql)
		{
			$base=$this->takeReader();
			$this->fromRow($base->getAssocRow("SELECT * FROM `".$this->table_name."` WHERE $where LIMIT 1"));
			if( $base->getResult() ) $this->is_saved=true;
			return (bool)$base->getResult();
		}
	}
	
	public function readById($id)
	{
		if (!SP_Helpers_Checker::checkID($id)) return false;
		return $this->readWhere("`".$this->fields['id']->getBaseName()."`='$id'");
	}
	
	public function defineName($value)
	{
		$this->name=$value;
		$array=SP_Common_Functions::splitCamelCase($value);
		$this->table_name=SP_Common_Functions::arrayToString($array, $glue='_');
		return $this;
	}
	
	public function validateBeforeSave($value)
	{
		$this->validate_before_save=(bool)$value;
	}
	
	public function validateBeforeSet($value)
	{
		$this->validate_before_set=(bool)$value;
	}
	
	public function turnOffValidation()
	{
		foreach($this->fields as $field)
		{
			$field->setNeedValidate(false);
		}
	}
	
	public function turnOnValidation()
	{
		foreach($this->fields as $field)
		{
			$field->setNeedValidate(true);
		}
	}
	
	public function defineTableName($value)
	{
		$this->table_name=$value;
		return $this;
	}
	
	public function takeName()
	{
		return $this->name;
	}
	
	public function takeTableName()
	{
		return $this->table_name;
	}
	
	public function defineBase($value)
	{
		$this->base = clone $value;
		return $this;
	}
	
	private function takeReader()
	{
		if( empty($this->reader) )
		{
			if( !SP_Database_BaseCollector::hasBase($this->base_name) ) throw new exception('Database "'.$this->base_name.'" not found');
			$this->reader=SP_Database_BaseCollector::getBase($this->base_name)->getReader();
		}
		return $this->reader;
	}
	
	private function takeWriter()
	{
		if( empty($this->writer) )
		{
			if( !SP_Database_BaseCollector::hasBase($this->base_name) ) throw new exception('Database "'.$this->base_name.'" not found');
			$this->writer=SP_Database_BaseCollector::getBase($this->base_name)->getWriter();
		}
		return $this->writer;
	}
	
	public function defineBaseName($value)
	{
		$this->base_name = $value;
		return $this;
	}
	
	public function isNew()
	{
		return !(bool)$this->takeField('id')->getValue();
	}
	
	public function isSaved()
	{
		return $this->is_saved;
	}
	
	public function undefine()
	{
		foreach($this->fields as $field)
		{
			$field->unsetValue();
		}
		foreach($this->relation_fields as $field)
		{
			$field->unsetValue();
		}
		$this->is_saved=false;
	}
	
}